open Monad;
open Operators;

module Path = Node.Path;
module Fs = Node.Fs;

/* We run the compiler as separate JS files during development, than as a bundle in production.
   We can only safely call __dirname in the main JS file, since other files will not be in the same path. */
let nodeDirname: string = [%bs.raw {| __dirname |}];
let packageJson: Js.Json.t = [%bs.raw {| require("../package.json") |}];
let packageVersion =
  packageJson |> Json.Decode.at(["version"], Json.Decode.string);

let scanResult =
  switch (CommandLine.Arguments.scan(Process.argv |> Array.to_list)) {
  | scanResult => scanResult
  | exception (CommandLine.Command.Unknown(message)) => Process.exit(message)
  };
let options = scanResult.options;

[@bs.val] [@bs.module "path"] external extname: string => string = "extname";

[@bs.val] [@bs.module "fs-extra"]
external ensureDirSync: string => unit = "ensureDirSync";

[@bs.val] [@bs.module "fs-extra"]
external copySync: (string, string) => unit = "copySync";

[@bs.module] external getStdin: unit => Js_promise.t(string) = "get-stdin";

let concat = (base, addition) => Path.join([|base, addition|]);

let annotation = "// Generated by Lona Compiler ";

/* TODO: Detect version already in file and swap it for new version */
let annotate = contents =>
  if (options.generateBannerMessage) {
    contents |> Js.String.includes(annotation)
      ? contents : annotation ++ packageVersion ++ "\n\n" ++ contents;
  } else {
    contents;
  };

let ensureDirAndWriteFile = (path, contents) => {
  ensureDirSync(Path.dirname(path));
  Fs.writeFileSync(path, contents, `utf8);
};

let writeAnnotatedFile = (path, contents) =>
  ensureDirAndWriteFile(path, annotate(contents));

let platformId = target =>
  switch (target) {
  | Types.JavaScript =>
    switch (options.javaScript.framework) {
    | JavaScriptOptions.ReactDOM => Types.ReactDOM
    | JavaScriptOptions.ReactNative => Types.ReactNative
    | JavaScriptOptions.ReactSketchapp => Types.ReactSketchapp
    }
  | Types.Swift =>
    switch (options.swift.framework) {
    | SwiftOptions.UIKit => Types.IOS
    | SwiftOptions.AppKit => Types.MacOS
    }
  | Types.Xml =>
    /* TODO: Replace when we add android */
    Types.IOS
  | Types.Reason => Types.ReasonCompiler
  };

let getTargetExtension =
  fun
  | Types.JavaScript => ".mjs"
  | Swift => ".swift"
  | Xml => ".xml"
  | Reason => ".re";

let formatFilename = (target, filename) =>
  switch (target) {
  | Types.Xml
  | Types.JavaScript
  | Types.Reason => Format.camelCase(filename)
  | Types.Swift => Format.upperFirst(Format.camelCase(filename))
  };

let renderColors = (target, config: Config.t) =>
  switch (target) {
  | Types.Swift => Swift.Color.render(config)
  | JavaScript => JavaScript.Color.render(config.colorsFile.contents)
  | Xml => Xml.Color.render(config.colorsFile.contents)
  | Reason => Process.exit("Reason not supported")
  };

let renderTextStyles = (target, config: Config.t) =>
  switch (target) {
  | Types.Swift => Swift.TextStyle.render(config)
  | JavaScript =>
    JavaScriptTextStyle.render(
      config.options.javaScript,
      config.colorsFile.contents,
      config.textStylesFile.contents,
    )
  | _ => ""
  };

let renderShadows = (target, config: Config.t) =>
  switch (target) {
  | Types.Swift => SwiftShadow.render(config)
  | JavaScript =>
    JavaScriptShadow.render(
      config.options.javaScript,
      config.colorsFile.contents,
      config.shadowsFile.contents,
    )
  | _ => ""
  };

/* TODO: Update this. SwiftTypeSystem.render now returns a different format */
let convertTypes = (target, contents) => {
  let json = contents |> Js.Json.parseExn;
  switch (target) {
  | Types.Swift =>
    let importStatement =
      switch (options.swift.framework) {
      | AppKit => "import AppKit\n\n"
      | UIKit => "import UIKit\n\n"
      };
    let types =
      json
      |> TypeSystem.Decode.typesFile
      |> SwiftTypeSystem.render(options)
      |> List.map((convertedType: SwiftTypeSystem.convertedType) =>
           convertedType.contents
         )
      |> Format.joinWith("\n\n");
    importStatement ++ types;
  | Types.JavaScript =>
    let types = json |> TypeSystem.Decode.typesFile;
    JavaScriptTypeSystem.renderToString(types);
  | Types.Reason =>
    let types = json |> TypeSystem.Decode.typesFile;
    ReasonTypeSystem.renderToString(options, types);
  | _ => Process.exit("Can't generate types for target")
  };
};

let convertLogic =
    (target, config: Config.t, resolvedProgramNode: LogicAst.syntaxNode, node) =>
  switch (target) {
  | Types.Xml => "Can't convert Logic to XML"
  | Types.Reason => "Converting Logic to Reason isn't supported yet"
  | Types.JavaScript =>
    node
    |> LogicJavaScript.convert(config, resolvedProgramNode)
    |> JavaScriptRender.toString
  | Types.Swift =>
    let importStatement =
      switch (options.swift.framework) {
      | AppKit => "import AppKit\n\n"
      | UIKit => "import UIKit\n\n"
      };
    let code =
      node
      |> LogicSwift.convert(config, resolvedProgramNode)
      |> SwiftRender.toString;

    importStatement ++ code;
  };

let convertColors = (target, config: Config.t) =>
  renderColors(target, config);

let convertTextStyles = (target, config: Config.t) =>
  renderTextStyles(target, config);

let convertShadows = (target, config: Config.t) =>
  renderShadows(target, config);

exception ComponentNotFound(string);

let getComponentRelativePath =
    (config: Config.t, sourceComponent, importedComponent) => {
  let sourcePath =
    Path.dirname(Config.Find.componentPath(config, sourceComponent));
  let importedPath = Config.Find.componentPath(config, importedComponent);
  let relativePath = Path.relative(~from=sourcePath, ~to_=importedPath, ());
  Js.String.startsWith(".", relativePath)
    ? relativePath : "./" ++ relativePath;
};

let getAssetRelativePath = (config: Config.t, sourceComponent, importedPath) => {
  let sourcePath =
    Path.dirname(Config.Find.componentPath(config, sourceComponent));
  let importedPath = Path.join2(config.workspacePath, importedPath);
  let relativePath = Path.relative(~from=sourcePath, ~to_=importedPath, ());
  Js.String.startsWith(".", relativePath)
    ? relativePath : "./" ++ relativePath;
};

let convertComponent = (config: Config.t, target, filename: string) => {
  let contents = Fs.readFileSync(filename, `utf8);
  let parsed = contents |> Js.Json.parseExn;
  let name = Path.basename_ext(filename, ".component");

  switch (target) {
  | Types.JavaScript =>
    JavaScriptComponent.generate(
      options.javaScript,
      name,
      Path.relative(
        ~from=Path.dirname(filename),
        ~to_=config.colorsFile.path,
        (),
      ),
      Path.relative(
        ~from=Path.dirname(filename),
        ~to_=config.shadowsFile.path,
        (),
      ),
      Path.relative(
        ~from=Path.dirname(filename),
        ~to_=config.textStylesFile.path,
        (),
      ),
      config,
      Path.relative(
        ~from=Path.dirname(filename),
        ~to_=config.workspacePath,
        (),
      ),
      getComponentRelativePath(config, name),
      getAssetRelativePath(config, name),
      parsed,
    )
    |> JavaScript.Render.toString
  | Swift =>
    let result =
      Swift.Component.generate(config, options, options.swift, name, parsed);
    result |> Swift.Render.toString;
  | _ => Process.exit("Unrecognized target")
  };
};

let copyStaticFiles = (target, outputDirectory) =>
  switch (target) {
  | Types.Swift =>
    let staticFiles =
      ["TextStyle", "CGSize+Resizing", "LonaViewModel"]
      @ (
        switch (options.swift.framework) {
        | UIKit => ["LonaControlView", "Shadow"]
        | AppKit => ["LNATextField", "LNAImageView", "NSImage+Resizing"]
        }
      );

    let frameworkExtension =
      switch (options.swift.framework) {
      | AppKit => "appkit"
      | UIKit => "uikit"
      };

    /* Some static files are generated differently depending on version */
    let versionString = filename =>
      switch (filename, options.swift.framework, options.swift.swiftVersion) {
      | ("TextStyle", UIKit, V4) => ".4"
      | ("TextStyle", UIKit, V5) => ".5"
      | _ => ""
      };

    staticFiles
    |> List.iter(file =>
         copySync(
           concat(
             nodeDirname,
             "static/swift/"
             ++ file
             ++ "."
             ++ frameworkExtension
             ++ versionString(file)
             ++ ".swift",
           ),
           concat(outputDirectory, file ++ ".swift"),
         )
       );
  | Types.JavaScript =>
    switch (options.javaScript.framework) {
    | ReactDOM =>
      let staticFiles = [
        "createActivatableComponent",
        "createFocusWrapper",
        "focusUtils",
      ];
      staticFiles
      |> List.iter(file =>
           copySync(
             concat(nodeDirname, "static/javaScript/" ++ file ++ ".js"),
             concat(outputDirectory, "utils/" ++ file ++ ".js"),
           )
         );
    | _ => ()
    }
  | _ => ()
  };

let findContentsAbove = contents => {
  let lines = contents |> Js.String.split("\n");
  let index =
    lines
    |> Js.Array.findIndex(line =>
         line |> Js.String.includes("LONA: KEEP ABOVE")
       );
  switch (index) {
  | (-1) => None
  | _ =>
    Some(
      (
        lines
        |> Js.Array.slice(~start=0, ~end_=index + 1)
        |> Js.Array.joinWith("\n")
      )
      ++ "\n\n",
    )
  };
};

let findContentsBelow = contents => {
  let lines = contents |> Js.String.split("\n");
  let index =
    lines
    |> Js.Array.findIndex(line =>
         line |> Js.String.includes("LONA: KEEP BELOW")
       );
  switch (index) {
  | (-1) => None
  | _ =>
    Some(
      "\n" ++ (lines |> Js.Array.sliceFrom(index) |> Js.Array.joinWith("\n")),
    )
  };
};

let updateContentsPreservingCommentMarkers = (originalPath, newContents) => {
  let (contentsAbove, contentsBelow) =
    switch (Fs.readFileAsUtf8Sync(originalPath)) {
    | existing => (findContentsAbove(existing), findContentsBelow(existing))
    | exception _ => (None, None)
    };
  let contents =
    switch (contentsAbove) {
    | Some(contentsAbove) => contentsAbove ++ newContents
    | None => newContents
    };
  let contents =
    switch (contentsBelow) {
    | Some(contentsBelow) => contents ++ contentsBelow
    | None => contents
    };
  contents;
};

exception FailedToEvaluate(string);

let generateDocumentation = (config: Config.t): TokenTypes.convertedWorkspace => {
  // While we do separately have the programs extracted from mdxFiles in documentFiles,
  // we (probably) convert from mdxFiles to logic files here to preserve node ids.
  // Otherwise, when generating code we won't be able to match up the the ids.
  // TODO: Revisit how this works
  let logicFiles: list(Config.file(LogicAst.syntaxNode)) =
    config.mdxFiles
    |> List.map((file: Config.file(MdxTypes.root)) => {
         let blockNodes = file.contents.children;
         let nodes =
           blockNodes
           |> Sequence.compactMap(node =>
                switch (node) {
                | MdxTypes.Code({parsed: Some(syntaxNode)}) =>
                  Some(syntaxNode)
                | _ => None
                }
              );
         {
           Config.path: file.path,
           contents:
             LogicAst.Program(
               Program(
                 nodes
                 |> Sequence.compactMap(LogicUtils.makeProgram)
                 |> LogicUtils.joinPrograms,
               ),
             ),
         };
       });

  let evaluationContext =
    LogicCompile.evaluate(config.logicLibraries, logicFiles);

  switch (evaluationContext) {
  | None =>
    Log.warn("Failed to evaluate workspace.");
    {flatTokensSchemaVersion: "0.0.1", files: []};
  | Some(evaluationContext) => {
      flatTokensSchemaVersion: "0.0.1",
      files:
        config.mdxFiles
        |> List.map((file: Config.file(MdxTypes.root)) => {
             let relativeInputPath =
               Path.relative(~from=config.workspacePath, ~to_=file.path, ());
             let basename =
               Path.basename_ext(
                 relativeInputPath,
                 extname(relativeInputPath),
               );
             let dirname = Path.dirname(relativeInputPath);
             let relativeOutputPath = Path.join2(dirname, basename ++ ".mdx");
             let blockNodes = file.contents.children;
             let data =
               blockNodes
               |> List.map(node =>
                    switch (node) {
                    | MdxTypes.Code(value) =>
                      let token =
                        value.parsed
                        >>= (
                          (syntaxNode: LogicAst.syntaxNode) =>
                            switch (syntaxNode) {
                            | TopLevelDeclarations(
                                TopLevelDeclarations({
                                  declarations: LogicAst.Next(declaration, _),
                                }),
                              ) =>
                              LogicFlatten.convertDeclaration(
                                config,
                                evaluationContext,
                                declaration,
                              )
                            | _ => None
                            }
                        )
                        |> map(TokenHtml.convert);
                      token %? "";
                    | _ =>
                      MdxTypes.Encode.encodeBlockNode(node)
                      |> Serialization.printMdxNode
                    }
                  )
               |> Format.joinWith("\n\n");
             {
               TokenTypes.inputPath: relativeInputPath,
               outputPath: relativeOutputPath,
               name: basename,
               contents:
                 DocumentationPage({
                   children:
                     MdxUtils.findChildPages(file.contents)
                     |> List.map(url =>
                          Path.join2(Path.dirname(relativeInputPath), url)
                        ),
                   mdxString: data,
                 }),
             };
           }),
    }
  };
};

let flattenWorkspace = (config: Config.t): TokenTypes.convertedWorkspace => {
  let evaluationContext =
    LogicCompile.evaluate(config.logicLibraries, config.logicFiles);
  switch (evaluationContext) {
  | None =>
    Log.warn("Failed to evaluate workspace.");
    {flatTokensSchemaVersion: "0.0.1", files: []};
  | Some(evaluationContext) => {
      flatTokensSchemaVersion: "0.0.1",
      files:
        config.logicFiles
        |> List.map((file: Config.file(LogicAst.syntaxNode)) => {
             let relativeInputPath =
               Path.relative(~from=config.workspacePath, ~to_=file.path, ());
             let basename =
               Path.basename_ext(
                 relativeInputPath,
                 extname(relativeInputPath),
               );
             let dirname = Path.dirname(relativeInputPath);
             let relativeOutputPath =
               Path.join2(dirname, basename ++ ".flat.json");
             let flatTokens =
               LogicFlatten.convert(config, evaluationContext, file.contents);
             {
               TokenTypes.inputPath: relativeInputPath,
               outputPath: relativeOutputPath,
               name: basename,
               contents: FlatTokens(flatTokens),
             };
           }),
    }
  };
};

let getLegacyTokensOutputPath =
    (config, target, toDirectory, inputPath, outputName) => {
  switch (target) {
  | Types.JavaScript =>
    let path =
      Config.Workspace.outputPathForWorkspaceFile(
        config,
        ~workspaceFile=inputPath,
      );

    Path.join2(
      Path.dirname(path),
      Path.basename_ext(path, ".json") ++ ".js",
    );
  | _ =>
    concat(
      toDirectory,
      formatFilename(target, outputName) ++ getTargetExtension(target),
    )
  };
};

let convertLegacyTokens = (config, target, toDirectory) => {
  renderColors(target, config)
  |> writeAnnotatedFile(
       getLegacyTokensOutputPath(
         config,
         target,
         toDirectory,
         config.colorsFile.path,
         "Colors",
       ),
     );
  renderTextStyles(target, config)
  |> writeAnnotatedFile(
       getLegacyTokensOutputPath(
         config,
         target,
         toDirectory,
         config.textStylesFile.path,
         "TextStyles",
       ),
     );
  if (target == Types.Swift || target == Types.JavaScript) {
    renderShadows(target, config)
    |> writeAnnotatedFile(
         getLegacyTokensOutputPath(
           config,
           target,
           toDirectory,
           config.shadowsFile.path,
           "Shadows",
         ),
       );
  };
};

let convertWorkspace = (target, workspace, output) => {
  let fromDirectory = Path.resolve(workspace, "");
  let toDirectory = Path.resolve(output, "");

  Config.load(
    platformId(target),
    options,
    workspace,
    toDirectory,
    nodeDirname,
  )
  |> Js.Promise.then_((config: Config.t) => {
       ensureDirSync(toDirectory);

       if (options.generateLegacyTokens) {
         convertLegacyTokens(config, target, toDirectory);
       };

       let resolvedProgramNode =
         LogicCompile.makeResolvedProgramNode(
           config.logicLibraries,
           config.logicFiles,
         );

       let userTypes = config.userTypesFile.contents;

       if (target == Types.Swift) {
         userTypes
         |> UserTypes.TypeSystem.toTypeSystemFile
         |> SwiftTypeSystem.render(options)
         |> List.iter((convertedType: SwiftTypeSystem.convertedType) => {
              let importStatement =
                switch (options.swift.framework) {
                | AppKit => "import AppKit\n\n"
                | UIKit => "import UIKit\n\n"
                };
              let outputPath =
                concat(
                  toDirectory,
                  formatFilename(target, convertedType.name)
                  ++ getTargetExtension(target),
                );
              writeAnnotatedFile(
                outputPath,
                importStatement ++ convertedType.contents,
              );
            });
       };

       if (target == Types.Swift || target == Types.JavaScript) {
         config.logicFiles
         |> List.iter((file: Config.file(LogicAst.syntaxNode)) => {
              let filename =
                formatFilename(
                  target,
                  Path.basename_ext(file.path, extname(file.path)),
                )
                ++ getTargetExtension(target);
              let dirname = Path.dirname(file.path);
              let outputPath =
                Config.Workspace.outputPathForWorkspaceFile(
                  config,
                  ~workspaceFile=Path.join2(dirname, filename),
                );

              /* Only convert non-empty files */
              switch (file.contents) {
              | TopLevelDeclarations(
                  TopLevelDeclarations({declarations: Next(_)}),
                ) =>
                let converted =
                  convertLogic(
                    target,
                    config,
                    resolvedProgramNode,
                    file.contents,
                  );
                writeAnnotatedFile(outputPath, converted);
              | _ => ()
              };
            });
       };

       copyStaticFiles(target, toDirectory);

       let successfulComponentNames =
         config.componentPaths
         |> List.filter(file =>
              switch (options.filterComponents) {
              | Some(value) => Js.Re.test_(Js.Re.fromString(value), file)
              | None => true
              }
            )
         |> List.map(file => {
              let fromRelativePath =
                Path.relative(~from=fromDirectory, ~to_=file, ());
              let toRelativePath =
                concat(
                  Path.dirname(fromRelativePath),
                  Path.basename_ext(fromRelativePath, ".component"),
                )
                ++ getTargetExtension(target);
              let outputPath = Path.join([|toDirectory, toRelativePath|]);
              Js.log(
                Path.join([|workspace, fromRelativePath|])
                ++ "=>"
                ++ Path.join([|output, toRelativePath|]),
              );
              switch (convertComponent(config, target, file)) {
              | exception (Json_decode.DecodeError(reason)) =>
                Js.log("Failed to decode " ++ file);
                Js.log(reason);
                None;
              | exception (Decode.UnknownParameter(name)) =>
                Js.log("Unknown parameter: " ++ name);
                None;
              | exception (Decode.UnknownExprType(name)) =>
                Js.log("Unknown expr name: " ++ name);
                None;
              | exception e =>
                Js.log("Unknown error");
                Js.log(e);
                None;
              | contents =>
                ensureDirSync(Path.dirname(outputPath));
                let (contentsAbove, contentsBelow) =
                  switch (Fs.readFileAsUtf8Sync(outputPath)) {
                  | existing => (
                      findContentsAbove(existing),
                      findContentsBelow(existing),
                    )
                  | exception _ => (None, None)
                  };
                let contents =
                  switch (contentsAbove) {
                  | Some(contentsAbove) => contentsAbove ++ contents
                  | None => contents
                  };
                let contents =
                  switch (contentsBelow) {
                  | Some(contentsBelow) => contents ++ contentsBelow
                  | None => contents
                  };
                Fs.writeFileSync(outputPath, annotate(contents), `utf8);

                Some(Path.basename_ext(fromRelativePath, ".component"));
              };
            })
         |> Sequence.compact;

       if (target == Types.Swift
           && options.swift.framework == UIKit
           && options.swift.generateCollectionView) {
         Fs.writeFileSync(
           concat(toDirectory, "LonaCollectionView.swift"),
           annotate(
             SwiftCollectionView.generate(
               config,
               options,
               options.swift,
               successfulComponentNames,
             ),
           ),
           `utf8,
         );
       };

       Config.Find.files(config, "**/*.png")
       |> List.map(file => {
            let fromRelativePath =
              Path.relative(~from=fromDirectory, ~to_=file, ());
            let outputPath = Path.join([|toDirectory, fromRelativePath|]);
            Js.log(
              Path.join([|workspace, fromRelativePath|])
              ++ "=>"
              ++ Path.join([|output, fromRelativePath|]),
            );
            copySync(file, outputPath);
          })
       |> Js.Promise.resolve;
     });
};
switch (scanResult.command) {
| Flatten(workspacePath) =>
  Config.load(Types.ReasonCompiler, options, workspacePath, "", nodeDirname)
  |> Js.Promise.then_(config =>
       switch (flattenWorkspace(config)) {
       | converted =>
         (converted |> TokenTypes.Encode.encodeConvertedWorkspace)
         ->(Js.Json.stringifyWithSpace(2))
         |> Js.log;
         Js.Promise.resolve();
       | exception (FailedToEvaluate(message)) => Process.exit(message)
       }
     )
  |> ignore
| Documentation(workspacePath) =>
  Config.load(Types.ReasonCompiler, options, workspacePath, "", nodeDirname)
  |> Js.Promise.then_(config =>
       switch (generateDocumentation(config)) {
       | converted =>
         (converted |> TokenTypes.Encode.encodeConvertedWorkspace)
         ->(Js.Json.stringifyWithSpace(2))
         |> Js.log;
         Js.Promise.resolve();
       | exception (FailedToEvaluate(message)) => Process.exit(message)
       }
     )
  |> ignore
| Workspace(target, workspacePath, outputPath) =>
  convertWorkspace(target, workspacePath, outputPath) |> ignore
| Component(target, input) =>
  Config.load(platformId(target), options, input, "", nodeDirname)
  |> Js.Promise.then_(config => {
       convertComponent(config, target, input) |> Js.log;
       Js.Promise.resolve();
     })
  |> ignore
| Colors(target, input) =>
  let initialWorkspaceSearchPath =
    switch (input) {
    | File(path) => path
    | Stdin => Process.cwd()
    };

  Config.load(
    platformId(target),
    options,
    initialWorkspaceSearchPath,
    "",
    nodeDirname,
  )
  |> Js.Promise.then_((config: Config.t) =>
       switch (input) {
       | Stdin =>
         getStdin()
         |> Js.Promise.then_(contents => {
              let config = {
                ...config,
                colorsFile: {
                  path: "__stdin__",
                  contents: Color.parseFile(contents),
                },
              };

              convertColors(target, config) |> Js.log;

              Js.Promise.resolve();
            })
       | File(_) =>
         convertColors(target, config) |> Js.log;

         Js.Promise.resolve();
       }
     )
  |> ignore;
| Shadows(target, input) =>
  let initialWorkspaceSearchPath =
    switch (input) {
    | File(path) => path
    | Stdin => Process.cwd()
    };

  Config.load(
    platformId(target),
    options,
    initialWorkspaceSearchPath,
    "",
    nodeDirname,
  )
  |> Js.Promise.then_((config: Config.t) =>
       switch (input) {
       | File(_) =>
         convertShadows(target, config) |> Js.log;

         Js.Promise.resolve();
       | Stdin =>
         getStdin()
         |> Js.Promise.then_(contents => {
              let config = {
                ...config,
                shadowsFile: {
                  path: "__stdin__",
                  contents: Shadow.parseFile(contents),
                },
              };

              convertShadows(target, config) |> Js.log;

              Js.Promise.resolve();
            })
       }
     )
  |> ignore;
| Types(target, inputPath, outputPath) =>
  let contents = Fs.readFileSync(inputPath, `utf8);
  let jsonContents = Serialization.convert(contents, "types", "json");
  let convertedContents = convertTypes(target, jsonContents);
  switch (outputPath) {
  | Some(path) =>
    updateContentsPreservingCommentMarkers(path, convertedContents)
    |> ensureDirAndWriteFile(path)
  | None => convertedContents |> Js.log
  };
| TextStyles(target, input) =>
  let initialWorkspaceSearchPath =
    switch (input) {
    | File(path) => path
    | Stdin => Process.cwd()
    };

  Config.load(
    platformId(target),
    options,
    initialWorkspaceSearchPath,
    "",
    nodeDirname,
  )
  |> Js.Promise.then_((config: Config.t) =>
       switch (input) {
       | File(_) =>
         convertTextStyles(target, config) |> Js.log;

         Js.Promise.resolve();
       | Stdin =>
         getStdin()
         |> Js.Promise.then_(contents => {
              let config = {
                ...config,
                textStylesFile: {
                  path: "__stdin__",
                  contents: TextStyle.parseFile(contents),
                },
              };

              convertTextStyles(target, config) |> Js.log;

              Js.Promise.resolve();
            })
       }
     )
  |> ignore;
| Config(workspacePath) =>
  Config.load(Types.ReactDOM, options, workspacePath, "", nodeDirname)
  |> Js.Promise.then_((config: Config.t) => {
       Js.log(config |> Config.toJson(packageVersion) |> Json.stringify);
       Js.Promise.resolve();
     })
  |> ignore
| Version => Js.log(packageVersion)
};